package com.tencent.sr.iris.activity.common.util;


import static java.nio.charset.StandardCharsets.UTF_8;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONAware;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;
import com.tencent.sr.iris.activity.common.config.QuFaConfig;
import com.tencent.sr.iris.activity.common.config.ScrmServerConfig;
import com.tencent.sr.iris.activity.common.enums.IrisErrorCode;
import com.tencent.sr.iris.activity.common.exception.BizCommErrorCode;
import com.tencent.sr.rmall.common.exception.retail.TRetailBizException;
import com.tencent.sr.rmall.common.exception.retail.TRetailRpcResultException;
import com.tencent.sr.rmall.common.request.PaginationRequest;
import com.tencent.sr.rmall.common.response.PaginationResponse;
import com.tencent.sr.rmall.springbootstarter.utils.JsonUtils;
import com.tencent.sr.tech.dynamic.config.spring.annotation.DynamicConfigValue;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * tencentcloud网关请求工具
 */
@Slf4j
@Component
public class QuFaHttpUtil {

    private static final String MAC_NAME = "HmacSHA1";
    private static final String ENCODING = "UTF-8";
    public static final String HTTP_METHOD_GET = "GET";
    public static final String HTTP_METHOD_POST = "POST";

    @DynamicConfigValue(key = "useOkHttpClient", defaultValue = "true")
    private boolean useOkHttpClient = true;

    @Resource
    private QuFaConfig config;
    @Resource
    private QuFaHttpUtilOkHttpClient okHttpClient;
    @Resource
    private ScrmServerConfig scrmServerConfig;

    public <T> PaginationResponse<T> postPageList(String path, Object body, Class<T> tClass) {
        JSONObject jsonRes = postCommon(config, path, JSON.toJSONString(body));
        JSONArray jsonData = jsonRes.getJSONObject("data").getJSONArray("data");
        List<T> dataList = JSON.parseArray(jsonData.toJSONString(), tClass);
        PaginationResponse<T> pageRes = new PaginationResponse<>();
        pageRes.setDataList(dataList);
        pageRes.setPageNum(jsonRes.getJSONObject("data").getInteger("current_page"));
        pageRes.setPageSize(jsonRes.getJSONObject("data").getInteger("per_page"));
        pageRes.setTotalCount(jsonRes.getJSONObject("data").getInteger("total"));
        return pageRes;
    }

    public <REQ> void postObjectNoResult(String path, REQ req) {
        String reqStr = JSON.toJSONString(req);
        JSONObject jsonObject = postCommon(config, path, reqStr);
        log.info("QuFaHttpUtil.postObjectNoResult :{}、{}、{}", path, reqStr, jsonObject);
    }

    public <REQ, RES> RES postObject(String path, REQ req, Class<RES> tClass) {
        JSONObject jsonRes = postCommon(config, path, JSON.toJSONString(req));
        JSONObject jsonData = Optional.ofNullable(jsonRes.getJSONObject("data")).orElseGet(JSONObject::new);
        return jsonData.toJavaObject(tClass);
    }

    public <REQ, RES> RES postObject(String path, REQ req, Class<RES> tClass, String fieldUnderData) {
        JSONObject jsonRes = postCommon(config, path, JSON.toJSONString(req));
        JSONObject jsonData = Optional.ofNullable(jsonRes.getJSONObject("data")).orElseGet(JSONObject::new);
        JSONObject jsonObject = Optional.ofNullable(jsonData.getJSONObject(fieldUnderData)).orElseGet(JSONObject::new);
        return jsonObject.toJavaObject(tClass);
    }

    public <REQ, RES> List<RES> postList(String path, REQ req, Class<RES> tClass) {
        JSONObject jsonRes = postCommon(config, path, JSON.toJSONString(req));
        JSONArray jsonData = jsonRes.getJSONArray("data");
        return JSON.parseArray(jsonData.toJSONString(), tClass);
    }

    public <REQ, RES> PaginationResponse<RES> postPage(String path,
            PaginationRequest<REQ> req, Class<RES> tClass) {
        JSONObject jsonObject = null;
        if (Objects.isNull(req.getQueryCriteria())) {
            jsonObject = new JSONObject();
        } else {
            jsonObject = (JSONObject) JSONObject.toJSON(req.getQueryCriteria());
        }
        jsonObject.put("page", req.getPageNum());
        jsonObject.put("per_page", req.getPageSize());

        JSONObject jsonRes = postCommon(config, path, jsonObject.toJSONString());
        PaginationResponse<RES> pageRes = new PaginationResponse<>();
        pageRes.setPageNum(jsonRes.getJSONObject("data").getInteger("current_page"));
        pageRes.setPageSize(jsonRes.getJSONObject("data").getInteger("per_page"));
        pageRes.setTotalCount(jsonRes.getJSONObject("data").getInteger("total"));
        JSONArray jsonData = jsonRes.getJSONObject("data").getJSONArray("data");
        if (Objects.nonNull(jsonData)) {
            List<RES> dataList = JSON.parseArray(jsonData.toJSONString(), tClass);
            pageRes.setDataList(dataList);
        }
        return pageRes;
    }

    public JSONObject postCommon(QuFaConfig config, String path, String reqBody) {
        if (useOkHttpClient) {
            return okHttpClient.postCommon(config, path, reqBody);
        }
        return postCommonHttpClient(config, path, reqBody);
    }


    private JSONObject postCommonHttpClient(QuFaConfig config, String path, String reqBody) {
        HttpPost httpPost = null;
        JSONObject jsonRes = null;
        try {
            String contentType = "application/json";
            String xDate = getGMTTime();
            String contentMD5 = base64Encode(getMD5(reqBody).getBytes());

            URL parsedUrl = new URL(config.getHost() + path);
            String pathAndParams = parsedUrl.getPath();
            if (parsedUrl.getQuery() != null) {
                pathAndParams = pathAndParams + "?" + sortQueryParams(parsedUrl.getQuery());
            }
            String stringToSign = String.format("x-date: %s\n%s\n%s\n%s\n%s\n%s",
                    xDate, HttpMethod.POST.name(), contentType, contentType, contentMD5, pathAndParams);
            byte[] hmacStr = hmacSHA1Encrypt(stringToSign, config.getAppSecret());
            String signature = base64Encode(hmacStr);
            String authHeader = String.format("hmac id=\"%s\", algorithm=\"hmac-sha1\", "
                    + "headers=\"x-date\", signature=\"%s\"", config.getAppKey(), signature);
            httpPost = new HttpPost(config.getHost() + path);
            httpPost.setHeader("Authorization", authHeader);
            httpPost.setHeader("Accept", contentType);
            httpPost.setHeader("Host", config.getHost()
                    .replace("http://", "").replace("https://", ""));
            httpPost.setHeader("x-date", xDate);
            httpPost.setHeader("Content-Type", contentType);

            httpPost.setHeader("Content-MD5", contentMD5);
            StringEntity stringEntity = new StringEntity(reqBody, ENCODING);
            httpPost.setEntity(stringEntity);
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
                String result = EntityUtils.toString(responseEntity, UTF_8);
                if (result == null) {
                    throw new TRetailBizException(BizCommErrorCode.HTTP_FAIL);
                }
                jsonRes = JsonUtils.parse(result, JSONObject.class);
                if (jsonRes == null) {
                    throw new TRetailBizException(BizCommErrorCode.HTTP_FAIL);
                }
                Integer code = jsonRes.getInteger("code");
                if (code == null) {
                    log.error("QuFaHttpUtil post error,path:{},requestBody:{},result:{}", path, reqBody, result);
                    throw new TRetailBizException(IrisErrorCode.REQUEST_QUFA_FAIL, jsonRes.getString("message"));
                } else if (code != 200) {
                    log.error("QuFaHttpUtil post error,path:{},requestBody:{},result:{}", path, reqBody, result);
                    throw new TRetailBizException(IrisErrorCode.REQUEST_QUFA_FAIL, jsonRes.getString("msg"));
                }
                log.info("QuFaHttpUtil post success,path:{},requestBody:{},result:{}", path, reqBody, result);
                return jsonRes;
            }
            log.error("QuFaHttpUtil post error,responseEntity is null, path:{},requestBody:{}", path, reqBody);
            throw new TRetailBizException(BizCommErrorCode.HTTP_FAIL);
        } catch (Exception e) {
            if (e instanceof TRetailBizException) {
                TRetailBizException exp = (TRetailBizException) e;
                throw new TRetailBizException(exp.getErrorCode(), exp.getMessage());
            }
            log.error("QuFaHttpUtil post error, path:{}, reqbody={} exception={}", path, reqBody,
                    ExceptionUtils.toString(e), e);
            throw new TRetailRpcResultException(IrisErrorCode.REQUEST_QUFA_FAIL);
        } finally {
            try {
                String curlStr = null;
                try {
                    curlStr = toCurl(httpPost);
                } catch (Exception e) {
                    log.warn("generate curl error", e);
                }
                if (Strings.isNullOrEmpty(curlStr)) {
                    ActiveSpan.tag("gateway_path", path);
                    ActiveSpan.tag("gateway_reqBody", reqBody);
                } else {
                    ActiveSpan.tag("gateway_input", curlStr);
                }
                ActiveSpan.tag("gateway_output", jsonRes == null ? null : jsonRes.toJSONString());
            } catch (Exception e) {
                log.warn("print curl error", e);
            }

        }
    }

    public String toCurl(HttpPost httpPost) throws Exception {
        if (httpPost == null) {
            return null;
        }
        String headers = Arrays.stream(httpPost.getAllHeaders())
                .map(header -> " -H '" + header.getName() + ":" + header.getValue() + "'")
                .collect(Collectors.joining());
        log.info("headers:{}", headers);
        String httpMethod = httpPost.getMethod().toUpperCase();
        String uri = httpPost.getURI().toString();
        String body = EntityUtils.toString(httpPost.getEntity(), UTF_8);
        String curl = String.format("curl %s -X %s %s -d '%s'", uri, httpMethod, headers, body);
        log.info("{}", curl);
        return curl;
    }

    private String getGMTTime() {
        Calendar cd = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        return sdf.format(cd.getTime());
    }

    private String sortQueryParams(String queryParam) {
        if (StringUtils.isEmpty(queryParam)) {
            return "";
        }

        Map<String, String> queryPairs = new TreeMap<>();
        String[] queryParams = queryParam.split("&");

        for (String query : queryParams) {
            String[] kv = query.split("=");
            queryPairs.put(kv[0], kv[1]);
        }

        StringBuilder sortedParamsBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : queryPairs.entrySet()) {
            sortedParamsBuilder.append(entry.getKey());
            sortedParamsBuilder.append("=");
            sortedParamsBuilder.append(entry.getValue());
            sortedParamsBuilder.append("&");
        }
        String sortedParams = sortedParamsBuilder.toString();
        sortedParams = sortedParams.substring(0, sortedParams.length() - 1);

        return sortedParams;
    }

    private byte[] hmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
        SecretKey secretKey = new SecretKeySpec(encryptKey.getBytes(ENCODING), MAC_NAME);
        Mac mac = Mac.getInstance(MAC_NAME);
        mac.init(secretKey);

        byte[] text = encryptText.getBytes(ENCODING);
        return mac.doFinal(text);
    }

    private String base64Encode(byte[] key) {
        final Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(key);
    }

    private String getMD5(String str) {
        return DigestUtils.md5Hex(str);
    }

    public String scrmRequest(String api, String reqBody, String httpMethod) {
        try {
            String environment = "";
            String url = scrmServerConfig.getUrl() + api + "?appkey=" + scrmServerConfig.getAppKey();
            String host = scrmServerConfig.getHost();
            String apiAppKey = scrmServerConfig.getApiAppKey();
            String apiAppSecret = scrmServerConfig.getApiAppSecret();
            String acceptHeader = "application/json";
            String contentType = "application/json";
            String contentMD5 = base64Encode(getMD5(reqBody).getBytes());

            if (httpMethod.toUpperCase() == HTTP_METHOD_GET) {
                reqBody = "";
            }
            // ContentType and contentMd5 should be empty if request body is not present
            if (reqBody.length() == 0) {
                contentType = "";
                contentMD5 = "";
                reqBody = "";
            }

            // Parse URL and assemble string to sign
            URL parsedUrl = new URL(url);
            String pathAndParams = parsedUrl.getPath();
            if (environment != "") {
                pathAndParams = pathAndParams.substring(pathAndParams.indexOf(environment) + environment.length());
            }

            if (parsedUrl.getQuery() != null) {
                pathAndParams = pathAndParams + "?" + sortQueryParams(parsedUrl.getQuery());
            }

            String xDate = getGMTTime();
            String stringToSign = String
                    .format("x-date: %s\n%s\n%s\n%s\n%s\n%s", xDate, httpMethod, acceptHeader, contentType, contentMD5,
                            pathAndParams);
            // Encode string with HMAC and base64
            byte[] hmacStr = hmacSHA1Encrypt(stringToSign, apiAppSecret);
            String signature = base64Encode(hmacStr);
            String authHeader = String
                    .format("hmac id=\"%s\", algorithm=\"hmac-sha1\", headers=\"x-date\", signature=\"%s\"", apiAppKey,
                            signature);
            Request request = null;
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = null;
            // Send request
            if (!parsedUrl.getHost().endsWith("gz.apigw.tencentcs.com")) {
                log.warn("scrmRequest fail, parsedUrl do not match,parsedUrl:{}", parsedUrl);
                return null;
            }
            if (httpMethod.equals(HTTP_METHOD_GET)) {
                HttpGet httpGet = new HttpGet(url);
                httpGet.setHeader("Accept", acceptHeader);
                httpGet.setHeader("Host", host);
                httpGet.setHeader("x-date", xDate);
                httpGet.setHeader("Authorization", authHeader);
                response = httpClient.execute(httpGet);
                log.info("scrmRequest success, method:{},url:{},allHeaders:{},response:{}", HTTP_METHOD_GET, url,
                        httpGet.getAllHeaders(), response);
            }

            if (httpMethod.equals(HTTP_METHOD_POST)) {
                HttpPost httpPost = new HttpPost(url);
                httpPost.setHeader("Accept", acceptHeader);
                httpPost.setHeader("Host", host);
                httpPost.setHeader("x-date", xDate);
                httpPost.setHeader("Content-Type", contentType);
                httpPost.setHeader("Content-MD5", contentMD5);
                httpPost.setHeader("Authorization", authHeader);
                StringEntity stringEntity = new StringEntity(reqBody, ENCODING);
                httpPost.setEntity(stringEntity);
                response = httpClient.execute(httpPost);
                log.info("scrmRequest success, method:{},url:{},allHeaders:{},reqBody:{},response:{}",
                        HTTP_METHOD_POST, url, httpPost.getAllHeaders(), reqBody, response);
            }

            // Receive response
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
                String result = EntityUtils.toString(responseEntity);
                log.info("scrmRequest success, method:{},url:{},reqBody:{},response:{}",
                        HTTP_METHOD_POST, url, reqBody, result);
                return result;
            }
            log.warn("scrmRequest fail, responseEntity is null, api:{},reqBody:{},httpMethod:{},apiKey:{}",
                    api, reqBody, HTTP_METHOD_POST, scrmServerConfig.getAppKey());

        } catch (Exception ex) {
            log.error("scrmRequest error, api:{},reqBody:{},httpMethod:{},apiKey:{},e:{}",
                    api, reqBody, HTTP_METHOD_POST, scrmServerConfig.getAppKey(), ex.getMessage(), ex);
        }
        return null;
    }
}
